home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
TeX 1995 July
/
TeX CD-ROM July 1995 (Disc 1)(Walnut Creek)(1995).ISO
/
dviware
/
dvibook
/
libtex
/
sdecode.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-03-18
|
14KB
|
643 lines
/*
* Copyright (c) 1989 University of Maryland
* Department of Computer Science. All rights reserved.
* Permission to copy for any purpose is hereby granted
* so long as this copyright notice remains intact.
*/
#ifndef lint
static char rcsid[] = "$Header: /usr/src/local/tex/local/mctex/lib/RCS/sdecode.c,v 3.4 89/11/06 15:01:07 chris Exp $";
#endif
#include <stdio.h>
#include <varargs.h>
#include "types.h"
#include "sdecode.h"
static char cclass[256]; /* XXX assumes 8-bit char */
#define CCL_SPACE 0x01 /* white space */
#define CCL_SEMI 0x02 /* `semicolon' (statement separator) */
#define isstopc(c) (cclass[c] != 0) /* both space and semi char */
static int sdset; /* flag says whether cclass[] set up */
static void args(), badarg();
static int scan(), scan_i(), scan_d();
extern char *strsave();
extern char *DVIFileName;
#ifndef BLOCK_COPY
/*
* Copy the text at `src' `downward' in memory for `len' bytes.
* This is like strcpy or memcpy, but handles one direction of
* overlap (when dst < src).
*/
static void
movedown(src, dst, len)
register char *src, *dst;
register int len;
{
while (--len >= 0)
*dst++ = *src++;
}
#endif
/*
* We have a number of states to do word interpretation. Outside words,
* we skip blanks and `semicolons'. The first non-blank, non-`semi' enters
* WORD state; in WORD state, double quote characters switch to
* QUOTE state, after which another double quote switches back to
* WORD state, and blanks and `semi' end WORD state, switching to DONE.
* While in WORD or QUOTE states, `c produces c for all characters c;
* to do this, we use two more states.
*/
#define S_DULL 0 /* not in a word */
#define S_WORD 1 /* in a word */
#define S_QUOTE 2 /* inside "" in a word */
#define S_BKQT1 3 /* not in "", but just read one ` */
#define S_BKQT2 4 /* in "", but just read one ` */
#define S_DONE 5 /* found end of word */
/*
* Structure used to communicate between the various functions.
*/
struct decode_info {
/* data used to read from input file */
int di_nch; /* number of characters remaining in buffer */
char *di_cp; /* next character to be read from buffer */
long di_nfilech; /* number of characters remaining in file */
FILE *di_file; /* the file itself */
/* information used and/or returned by the canon routine */
int di_lexstate; /* lexical state */
int di_call; /* set when we reach terminating `semicolon' */
/* information used more globally */
char *di_word; /* last word from word() */
char *di_kw; /* the keyword */
int di_bkw; /* true iff keyword is in static buffer */
};
/*
* `Edit' the text in the buffer in-place.
* Return the count of characters resulting from the edit (e.g.,
* from `foo`"bar' the count would be 7).
* Set di_call if we reach state S_DONE by encountering a `semicolon'.
* The edited text begins wherever di_cp points.
* We begin the edit in the state given by di_lexstate.
*/
static int
canon(di)
register struct decode_info *di;
{
register int state, c, n;
register char *cp, *outp, *s;
state = di->di_lexstate;
n = di->di_nch;
cp = outp = di->di_cp;
while (--n >= 0) {
c = *cp++;
switch (state) {
case S_DULL:
if (isstopc(c))
continue;
state = S_WORD;
/* FALLTHROUGH */
case S_WORD:
if (c == '"') {
state = S_QUOTE;
continue;
}
if (c == '`') {
state = S_BKQT1;
continue;
}
/* look for `semicolon' (call) characters */
if (cclass[c] & CCL_SEMI)
di->di_call = 1;
if (isstopc(c)) {
state = S_DONE;
goto done;
}
break;
case S_QUOTE:
if (c == '`') {
state = S_BKQT2;
continue;
}
if (c == '"') {
state = S_WORD;
continue;
}
break;
case S_BKQT1:
state = S_WORD;
break;
case S_BKQT2:
state = S_QUOTE;
break;
default:
panic("sdecode canon");
/* NOTREACHED */
}
*outp++ = c;
}
n = 0;
done:
c = outp - di->di_cp; /* wrote this many chars */
di->di_lexstate = state;/* save new state */
di->di_nch = n; /* and new counts and pointers */
di->di_cp = cp;
return (c);
}
#define WDMAX BUFSIZ /* when testing, make this small */
/*
* Read a word from the input file.
* Returns NULL (and sets di_call) at end of \special.
*
* We gather up to WDMAX + k characters at a time into an input
* buffer, and lex them using `canon' above. The result is at most
* as long as the original text. Since a single legal word might
* be up to WDMAX bytes long, k must be at least 1 (for the trailing
* NUL). If the input word is longer, we flush the trailing part,
* using the k `extra' bytes (and again k must be at least 1).
*
* If there is a current keyword and it is in the current buffer
* (di_bkw != 0), and we have to move existing buffer text around
* or write over it with new file text, we save the keyword and
* clear di_bkw.
*/
static char *
word(di, quietly)
register struct decode_info *di;
int quietly;
{
register int n;
register char *p;
register int len, trunc = 0;
static char inbuf[WDMAX + 20]; /* k=20 */
len = 0; /* no word bytes yet */
p = di->di_cp; /* where canon() writes (nil if first time) */
di->di_lexstate = S_DULL;
/*
* Loop invariants:
* len is in [0..WDMAX)
* di_lexstate != S_DONE
*/
do {
/* if the buffer is empty, refill it */
if (di->di_nch == 0) {
/* compute remaining buffer space */
n = sizeof inbuf - len;
/* read n bytes or rest of string, whichever smaller */
if (di->di_nfilech < n && (n = di->di_nfilech) == 0) {
/* `eof': fake up a call-terminator */
di->di_call = 1;
if (di->di_lexstate == S_DULL)
return (di->di_word = NULL);
break; /* take partial word */
}
/* save keyword if this will clobber it */
if (di->di_bkw) {
di->di_kw = strsave(di->di_kw);
di->di_bkw = 0;
}
/* if there is some canon text, move it to front */
if (len && p > inbuf) {
#ifdef BLOCK_COPY
bcopy(p, inbuf, len);
#else
movedown(p, inbuf, len);
#endif
}
/* canon text is (or will be) at front now */
p = inbuf;
di->di_cp = p + len;
if (fread(di->di_cp, 1, n, di->di_file) != n) {
if (ferror(di->di_file))
error(1, -1, "error reading %s",
DVIFileName);
GripeUnexpectedDVIEOF();
}
di->di_nfilech -= n;
di->di_nch = n;
}
n = canon(di);
if ((len += n) > WDMAX) {
len = WDMAX;
trunc = 1;
}
} while (di->di_lexstate != S_DONE);
p[len] = 0;
/*
* If the word was too long (and hence truncated), complain
* (unless we are quietly skipping over the remains of a
* broken \special).
*/
if (trunc && !quietly) {
/* word was too long; complain */
error(0, 0,
#if WDMAX > 20
"over-long word in \\special: `%.20s...'",
#else
"over-long word in \\special: `%s...'",
#endif
p);
error(0, 0, "(truncated to %d characters)", WDMAX);
}
return (di->di_word = p);
}
/*
* Initialise the character class table.
*/
void
SDsetclass(spaces, semis)
register char *spaces;
register char *semis;
{
register int c;
if (spaces == NULL)
spaces = "\b\t\n\f\r ";
if (semis == NULL)
semis = ";\n";
for (c = 0; c < sizeof cclass / sizeof *cclass; c++)
cclass[c] = 0;
while ((c = *spaces++) != 0)
cclass[c] |= CCL_SPACE;
while ((c = *semis++) != 0)
cclass[c] |= CCL_SEMI;
sdset = 1;
}
/*
* Perform a binary search for the given string in the given table.
*/
static struct sdecode *
lookup(str, p, max)
register char *str;
register struct sdecode *p;
register int max;
{
register struct sdecode *m;
register int dir;
while (max > 0) {
/*
* Look at the median; pick the right hand one if
* there are two such entries (when `max' is even).
* Set dir positive if str is `greater than' m->sd_name,
* negative if it is `less'. If not equal, move
* left or right accordingly. When max is even and
* we move right, we have to use (max-1)/2 since we
* broke the tie towards the right.
*/
m = p + (max >> 1);
if ((dir = *str - *m->sd_name) == 0)
dir = strcmp(str, m->sd_name);
if (dir == 0) /* found */
return (m);
if (dir > 0) { /* str > mid: move right */
p = m + 1;
max = (max - 1) >> 1;
} else /* str < mid: move left */
max >>= 1;
}
return (NULL);
}
/*
* Decode a series of \special keywords.
*
* The table points to a sorted list of keywords to match.
* On a match, the appropriate function is called with appropriate
* arguments.
*
* Characters in `semi' (if not NULL) act like `statement separators'.
* For instance, decoding with semi=>";\n" means that
*
* \special{foo 1; bar 2 4^^Jbaz 3}
*
* tries to call foo(1), bar(2,4), and baz(3) (in that order).
*/
void
SDecode(fp, len, table, tsize)
FILE *fp;
register i32 len;
struct sdecode *table;
int tsize;
{
register char *cp;
register struct sdecode *tp;
register int h;
struct decode_info di;
/* set defaults, if necessary */
if (!sdset)
SDsetclass((char *)NULL, (char *)NULL);
di.di_nch = 0;
di.di_cp = NULL;
di.di_nfilech = len;
di.di_file = fp;
di.di_bkw = 0;
for (;;) {
di.di_call = 0;
if ((cp = word(&di, 0)) == NULL)
return; /* (all done) */
tp = lookup(cp, table, tsize);
if (tp == NULL) {
error(0, 0,
"Warning: unrecognised \\special keyword `%s' ignored", cp);
/* di_call is not set, so fall through */
} else {
di.di_kw = cp;
di.di_bkw = 1;
args(tp, &di);
if (!di.di_call)
error(0, 0,
"Warning: extra arguments to \\special `%s' ignored", di.di_kw);
if (!di.di_bkw)
free(di.di_kw);
di.di_bkw = 0;
}
/* eat extra arguments, or arguments to an unknown function */
while (!di.di_call)
(void) word(&di, 1);
}
}
/*
* Gather up the arguments to the given function, then (if all goes well)
* call that function with those arguments. On return, if di_call is not
* set, there were extra arguments left over.
*/
static void
args(tp, di)
register struct sdecode *tp;
register struct decode_info *di;
{
char *fmt;
int n, room, f1;
i32 *ip, i[4];
double d[2];
switch (tp->sd_args) {
case sda_none:
(*tp->sd_fn)(tp->sd_name);
return;
case sda_s:
fmt = "s";
if (scan(di, &fmt))
break;
(*tp->sd_fn)(tp->sd_name, di->di_word);
return;
case sda_d:
fmt = "d";
if (scan(di, &fmt, &i[0]))
break;
(*tp->sd_fn)(tp->sd_name, i[0]);
return;
case sda_f:
fmt = "f";
if (scan(di, &fmt, &d[0]))
break;
(*tp->sd_fn)(tp->sd_name, d[0]);
return;
case sda_dd:
fmt = "dd";
if (scan(di, &fmt, &i[0], &i[1]))
break;
(*tp->sd_fn)(tp->sd_name, i[0], i[1]);
return;
case sda_ff:
fmt = "ff";
if (scan(di, &fmt, &d[0], &d[1]))
break;
(*tp->sd_fn)(tp->sd_name, d[0], d[1]);
return;
case sda_ddddff:
fmt = "ddddff";
if (scan(di, &fmt, &i[0], &i[1], &i[2], &i[3], &d[0], &d[1]))
break;
(*tp->sd_fn)(tp->sd_name, i[0], i[1], i[2], i[3], d[0], d[1]);
return;
case sda_nd:
f1 = 'd';
goto array;
case sda_nx:
f1 = 'x';
goto array;
case sda_rest:
(*tp->sd_fn)(tp->sd_name,
(i32)di->di_nch, di->di_cp, (i32)di->di_nfilech);
di->di_call = 1;
di->di_nch = 0;
di->di_nfilech = 0;
return;
default:
panic("sdecode args(): sd_argtype");
/* NOTREACHED */
}
/*
* If we get here, there is a missing or incorrect argument.
*/
badarg(*fmt, di);
return;
array:
ip = (i32 *)malloc((unsigned)(room = 10) * sizeof(i32));
if (ip == NULL)
goto nomem;
n = 0;
while (!di->di_call && word(di, 0) != NULL) {
if (scan_i(di->di_word, f1, &i[0])) {
badarg(f1, di);
free((char *)ip);
return;
}
if (n == room) {
room += 10;
ip = (i32 *)realloc((char *)ip,
(unsigned)room * sizeof(i32));
if (ip == NULL)
goto nomem;
}
ip[n++] = i[0];
}
(*tp->sd_fn)(tp->sd_name, n, ip);
free((char *)ip);
return;
nomem:
/*
* might as well stop: if malloc() failed, we will probably get
* nowhere fast anyway.
*/
error(1, -1,
"ran out of memory allocating %d bytes for \\special `%s'",
n * sizeof(i32), di->di_kw);
/* NOTREACHED */
}
/*
* Complain about an argument. Scan() has set di_word to NULL if
* it was missing; otherwise it is incorrect.
*/
static void
badarg(c, di)
int c;
struct decode_info *di;
{
char *s;
switch (c) {
case 'd':
s = "decimal";
break;
case 'f':
s = "floating";
break;
case 's':
s = "string"; /* must be `missing', not `bad' */
break;
case 'x':
s = "hexadecimal";
break;
default:
panic("sdecode badarg(%c)", c);
/* NOTREACHED */
}
if (di->di_word == NULL)
error(0, 0, "missing %s argument for \\special `%s'",
s, di->di_kw);
else {
error(0, 0, "bad %s argument to \\special `%s': `%s'",
s, di->di_kw, di->di_word);
error(0, 0, "(this part of the \\special will be ignored)");
while (!di->di_call)
(void) word(di, 1);
}
}
static int
scan(va_alist)
va_dcl
{
register struct decode_info *di;
register char *fmt;
register int c;
char **fmtp;
va_list ap;
va_start(ap);
di = va_arg(ap, struct decode_info *);
fmtp = va_arg(ap, char **);
fmt = *fmtp;
while ((c = *fmt++) != 0) {
if (di->di_call || word(di, 0) == NULL) {
di->di_word = NULL;
goto out;
}
switch (c) {
/* BEWARE, can only handle one string */
case 's': /* always accepted */
break;
case 'd':
case 'x':
if (scan_i(di->di_word, c, va_arg(ap, i32 *)))
goto out;
break;
case 'f':
if (scan_d(di->di_word, va_arg(ap, double *)))
goto out;
break;
default:
panic("sdecode scan() *fmt=%c", c);
/* NOTREACHED */
}
}
c = 0;
out:
va_end(ap);
*fmtp = fmt - 1;
return (c);
}
static int
scan_i(p, c, result)
char *p;
int c;
i32 *result;
{
#ifdef HAVE_STRTOL
char *end;
long strtol();
*result = strtol(p, &end, c == 'x' ? 16 : 10);
return (*end);
#else
long l;
char junk;
if (sscanf(p, c == 'x' ? "%lx%c" : "%ld%c", &l, &junk) != 1)
return (1);
*result = l;
return (0);
#endif
}
static int
scan_d(p, result)
char *p;
double *result;
{
#ifdef HAVE_STRTOD
char *end;
double strtod();
*result = strtod(p, &end);
return (*end);
#else
char junk;
return (sscanf(p, "%lf%c", result, &junk) != 1);
#endif
}